<?php
namespace app\api\controller;

use think\Exception;
use think\facade\{Log, Db};

use app\BaseController;

use app\common\model\{User, Order};

class WxpayController extends BaseController
{
    protected $appid;
    protected $mch_id;
    protected $key;
    protected $openid;
    protected $out_trade_no;
    protected $body;
    protected $total_fee;
    protected $notify;
    
    public function __construct(){
        $system = getSystemVal();

        $this->appid  = $system['appid'];
        $this->mch_id = $system['mch_id'];
        $this->key    = $system['wx_key'];
    }

    public function pay($data)
    {
        $this->openid       = $data['openid'];
        $this->out_trade_no = $data['order_no'];
        $this->total_fee    = $data['total_fee'];
        $this->body         = $data['body'];
        $this->notify       = $data['notify'];
        return $this->wxpay();
    }

    //微信小程序接口
    public function wxpay(){  //统一下单接口
        $unifiedorder = $this->unifiedorder();

        if($unifiedorder['return_code'] == 'SUCCESS' && $unifiedorder['result_code'] == 'SUCCESS'){//成功
            $parameters = array(
                'appId'     => $this->appid,
                'timeStamp' => '' . time() . '',
                'nonceStr'  => $this->createNoncestr(),
                'package'   => 'prepay_id=' . $unifiedorder['prepay_id'],
                'signType'  => 'MD5'
            );

            /** 签名 */
            $parameters['paySign'] = $this->getSign($parameters);

            $data = [
                'code' => 200,
                'sign' => $parameters,
            ];

            return $data;
        }else{
            $data = [
                'return_msg' => $unifiedorder['return_msg'],
                'code' => 400,
            ];

            if($unifiedorder['return_code'] == 'SUCCESS'){
                $data['return_msg'] = $unifiedorder['err_code_des'];
            }

            return $data;
        }
    }

    //统一下单接口
    private function unifiedorder()
    {
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

        $parameters = array(
            'appid'            => $this->appid, /** 小程序ID */
            'mch_id'           => $this->mch_id,  /** 商户号 */
            'nonce_str'        => $this->createNoncestr(),  /** 随机字符串 */
            'body'             => $this->body,//商品描述
            'out_trade_no'     => $this->out_trade_no,  /** 商户订单号 */
            // 'total_fee'        => floatval($this->total_fee * 100),  /** 总金额（单位：分） */
            'total_fee'        => 1,  /** 总金额（单位：分） */
            'spbill_create_ip' => $this->get_client_ip(),  /** 终端IP */
            'notify_url'       => config('app.url') . '/api/wxpay/' . $this->notify,  /** 回调地址 */
            'openid'           => $this->openid,  /** 用户openid */
            'trade_type'       => 'JSAPI',  /** 交易类型 */
        );

        // 统一下单签名
        $parameters['sign'] = $this->getSign($parameters);
        $xmlData = $this->arrayToXml($parameters);

        $return = $this->xmlToArray($this->postXmlCurl($xmlData, $url, 60));

        return $return;
    }

    // 支付回调
    public function notify()
    {
        //接收微信返回的数据数据,返回的xml格式
        $xmlData = file_get_contents('php://input');

        if(!$xmlData){
            return $this->arrayToXml(['return_msg' => 'ERROR', 'return_code' => 'FAIL']);
        }

        //将xml格式转换为数组
        $data = $this->xmlToArray($xmlData);

        //为了防止假数据，验证签名是否和返回的一样。
        if($this->checkSign($data, $this->key)){
            if($data['return_code'] == 'SUCCESS' && $data['result_code'] == 'SUCCESS'){
                Db::startTrans();
                try {
                    /** 订单号 */
                    $orderNo = $data['out_trade_no'];

                    $orderModel = new Order();
                    $userModel  = new User();

                    $orderInfo = $orderModel->getFind(['order_no' => $orderNo]);

                    if($orderInfo['code'] != 'ok'){
                        throw new Exception(50001);
                    }

                    /** 更新订单状态 */
                    $orderModel->edit(['id' => $orderInfo['data']['id'], 'status' => 2, 'pay_time' => time()]);

                    /** 更新会员等级 */
                    $userModel->reckonGrade($orderInfo['data']['user_id'], $orderInfo['data']['total']);

                    /** 发放推荐奖 */
                    $userModel->pushAward($orderInfo['data']['user_id'], $orderInfo['data']['total'], $orderInfo['data']['id']);

                    /** 检测并发放团队奖 */
                    $userModel->teamAward($orderInfo['data']['user_id']);

                    Db::commit();    
                }catch(Exception $e){
                    Db::rollback();
                    Log::write($e->getMessage());
                    return $this->arrayToXml(['return_msg' => 'ERROR', 'return_code' => $e->getMessage()]);
                }

                // return json(['code' => 'SUCCESS', 'message' => '成功']);
                return $this->arrayToXml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']);
            }
        }

        return $this->arrayToXml(['return_msg' => 'ERROR', 'return_code' => 'FAIL']);
    }

    private function getSigns($arr,$key)
    {
        ksort($arr);
        foreach ($arr as $k => $v) {
            if (empty($v)) {
                unset($arr[$k]);
            }
        }

        ksort($arr);

        $stringA = $this->arrToStr($arr);
        return strtoupper(md5($stringA . "&key=".$key));
    }

    //作用：产生随机字符串，不长于32位
    private function createNoncestr($length = 32) {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    private function arrToStr($arr)
    {
        ksort($arr);
        $str = '';
        foreach ($arr as $k => $v) {
            $str .= $k . "=" . $v . "&";
        }

        return rtrim($str,"&");
    }

    private static function postXmlCurl($xml, $url, $second = 30)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);

        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, 40);
        set_time_limit(0);

        //运行curl
        $data = curl_exec($ch);

        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            echo "curl出错，错误码:$error" . "<br>";
            // throw new WxPayException("curl出错，错误码:$error");
        }
    }

    //数组转换成xml
    private function arrayToXml($arr)
    {
        $xml = "<root>";
        foreach ($arr as $key => $val) {
            if (is_array($val)) {
                $xml .= "<" . $key . ">" . $this->arrayToXml($val) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            }
        }

        $xml .= "</root>";
        return $xml;
    }

    private function checkSign($data,$key)
    {
        $sign_return = $data['sign'];
        unset($data['sign']);
        $sign = $this->getSigns($data,$key);

        if ($sign_return != $sign) {
            return false;
        }

        return true;
    }

    //xml转换成数组
    private function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring), true);

        return $val;
    }

    /** 获取当前服务器的IP */
    private function get_client_ip()
    {
        if ($_SERVER['REMOTE_ADDR']) {
            $cip = $_SERVER['REMOTE_ADDR'];
        } elseif (getenv("REMOTE_ADDR")) {
            $cip = getenv("REMOTE_ADDR");
        } elseif (getenv("HTTP_CLIENT_IP")) {
            $cip = getenv("HTTP_CLIENT_IP");
        } else {
            $cip = "unknown";
        }
        return $cip;
    }

    //作用：生成签名
    private function getSign($Obj)
    {
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = $this->formatBizQueryParaMap($Parameters, false);
        //签名步骤二：在string后加入KEY
        $String = $String . "&key=" . $this->key;

        //签名步骤三：MD5加密
        $String = md5($String);
        //签名步骤四：所有字符转为大写
        $result_ = strtoupper($String);
        return $result_;
    }

    ///作用：格式化参数，签名过程需要使用
    private function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);

        foreach($paraMap as $k => $v){
            if($urlencode){
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }

        $reqPar = '';

        if(strlen($buff) > 0){
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }

        return $reqPar;
    }
}